import json
import os
from pathlib import Path
from pprint import pprint

import requests
from dotenv import load_dotenv, set_key

import errors
import models

load_dotenv()
EMAIL = os.environ.get("EMAIL")
PASSWORD = os.environ.get("PASSWORD")


def to_json(target, data):
    with open(f"{target}.json", "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)


class MonstercatAPI:
    """Class for a Monstercat API

    Attributes:
        session (Session): current session for sending requests
        base_url (str): the base URL of the API
    """

    CDX = "https://cdx.monstercat.com"
    BASE = "https://player.monstercat.app/api"
    SIGN_IN = BASE + "/sign-in"
    SIGN_OUT = BASE + "/sign-out"

    CATALOG = BASE + "/catalog"
    RELEASES = BASE + "/releases"
    RELATED_RELEASES = BASE + "/related-releases"
    ARTISTS = BASE + "/artists"
    ARTIST = BASE + "/artist"
    LATEST_ARTISTS = BASE + "/latest-artists"

    BROWSE = CATALOG + "/browse"
    FILTERS = CATALOG + "/filters"
    TRACK = CATALOG + "/track"
    RELEASE = CATALOG + "/release"
    LATEST_RELEASES = CATALOG + "/latest-releases"
    STREAM_TRACK = BASE + "/release/{release_uuid}/track-stream/{track_uuid}"
    DOWNLOAD_TRACK = BASE + "/release/{release_uuid}/track-download/{track_uuid}"
    EXTENDED_TRACK = BASE + "/file/{file_uuid}/open"
    RELATED_RELEASES = BASE + "/related-releases/{release_uuid}"

    def __init__(self) -> None:
        """Create an instance of the MonstercatAPI class

        Args:
            email (str): input email from the account.
            password (str): input password from the account.
        """
        self.session = requests.Session()

    def bad_response(self, response: requests.Response) -> None:
        match response.status_code:
            case 400:
                raise errors.BadRequest(response=response)
            case 401:
                raise errors.Unauthorized(response=response)
            case 403:
                raise errors.Forbidden(response=response)
            case 404:
                raise errors.NotFound(response=response)
            case 500:
                raise errors.IntervalServerError(response=response)
            case 502:
                raise errors.BadGateway(response=response)
            case 503:
                raise errors.ServiceUnavalaible(response=response)

    def sign_in_email(self, email: str, password: str) -> models.SignInResponse:
        """Login with email, password and 2FA

        Args:
            email (str): input email from the account.
            password (str): input password from the account.

        Returns:
            models.SignInResponse: if successful,
                raise error with HTTP status_code and reason otherwise.
        """
        if "COOKIE" in os.environ and os.environ.get("COOKIE") != "":
            cookie = os.environ.get("COOKIE")
            self.session.cookies.set(
                name="cid", value=cookie, domain="player.monstercat.app"
            )
            return
        payload = {"Email": email, "Password": password}
        url = self.SIGN_IN
        response = self.session.post(
            url=url,
            json=payload,
        )
        print(self.session.cookies.get_dict())
        if response.status_code == 200:
            set_key(
                dotenv_path=Path(".env"),
                key_to_set="COOKIE",
                value_to_set=self.session.cookies.get_dict()["cid"],
            )
            return models.SignInResponse.from_json(json.dumps(response.json()))
        else:
            self.bad_response(response=response)

    def sign_in_2fa(
        self, email: str, password: str, token: str
    ) -> models.SignInResponse:
        """Login with email, password and 2FA

        Args:
            email (str): input email from the account.
            password (str): input password from the account.
            token (str): input token for 2FA.

        Returns:
            models.SignInResponse: if successful,
                raise error with HTTP status_code and reason otherwise.
        """
        payload = {"Email": email, "Password": password}
        url = self.SIGN_IN
        response = self.session.post(
            url=url,
            json=payload,
        )
        if response.status_code == 200:
            return models.SignInResponse.from_json(json.dumps(response.json()))
        else:
            self.bad_response(response=response)

    def sign_out(self):
        set_key(
            dotenv_path=Path(".env"),
            key_to_set="COOKIE",
            value_to_set="",
        )
        url = self.SIGN_OUT
        response = self.session.post(
            url=url,
        )
        print(response.status_code)

    def browse_releases(
        self,
        search: str = None,
        limit: int = 50,
        offset: int = 0,
    ) -> models.ReleasesResponse:
        """Browse and search releases.

        Args:
            search (str, optional): The search terms.
                Defaults to None.
            limit (int, optional): Amount of releases to return (max 50).
                Defaults to 50.
            offset (int, optional): Amount of releases to skip.
                Defaults to 0.

        Returns:
            models.ReleasesResponse: if successful,
                raise error with HTTP status_code and reason otherwise.
        """
        params = {
            "limit": limit,
            "offset": offset,
            "search": search,
        }
        url = self.RELEASES
        response = self.session.get(
            url=url,
            params=params,
        )
        if response.status_code == 200:
            to_json("releases", response.json())
            return models.ReleasesResponse.from_json(
                json.dumps(response.json()["Releases"])
            )
        else:
            self.bad_response(response=response)

    def get_latests_releases(
        self,
        limit: int = 100,
        offset: int = 0,
        sort: str = "-date",
        creator_friendly: bool = False,
    ) -> models.LatestReleasesResponse:
        """Browse recently released releases

        Args:
            limit (int, optional): Amount of releases to return.
                Defaults to 100.
            offset (int, optional): Amount of releases to skip.
                Defaults to 0.
            sort (str, optional): Field to sort by. Prefix with `-` for descending.
                Defaults to "-date".
            creator_friendly (bool, optional): Whether to filter for creator safe or unsafe releases.
                Defaults to False.

        Returns:
            models.LatestReleasesResponse: if successful,
                raise error with HTTP status_code and reason otherwise.
        """
        params = {
            "limit": limit,
            "offset": offset,
            "sort": sort,
            "creatorFriendly": creator_friendly,
        }
        url = self.LATEST_RELEASES
        response = self.session.get(
            url=url,
            params=params,
        )
        if response.status_code == 200:
            return models.LatestReleasesResponse.from_json(json.dumps(response.json()))
        else:
            self.bad_response(response=response)

    def get_release(self, catalog_id: str) -> models.ReleaseResponse:
        """Get release by catalog_id

        Args:
            catalog_id (str): Release.CatalogId

        Returns:
            models.ReleaseResponse: if successful,
                raise error with HTTP status_code and reason otherwise.
        """
        url = f"{self.RELEASE}/{catalog_id}"
        response = self.session.get(url=url)
        if response.status_code == 200:
            return models.ReleaseResponse.from_json(json.dumps(response.json()))
        else:
            self.bad_response(response=response)

    def get_cover(
        self, catalog_id: str, width: int = "3000", encoding: str = "jpeg"
    ) -> str:
        """The CDX system delivers resized and re-encoded images to be displayed on the website.
        Redirects to the resized image under cdx.monstercat.com/resized/{id}/{width}.{encoding}.
        If the width and encoding pair are not supported, redirects to the original URL instead.

        Args:
            catalog_id (str): Release.CatalogId
            width (int, optional): Width of the image (supported: webp: 300, 600, 1024, jpeg: 3000).
                Defaults to "3000".
            encoding (str, optional): Encoding of the image (supported: webp, jpeg).
                Defaults to "jpeg".

        Returns:
            str: str with cover url if successful, raise error with HTTP status_code and reason otherwise.
        """
        params = {
            "width": width,
            "encoding": encoding,
            "url": f"https://www.monstercat.com/release/{catalog_id}/cover",
        }
        url = self.CDX
        response = self.session.get(
            url=url,
            params=params,
        )
        if response.status_code == 200:
            return response.url
        else:
            self.bad_response(response=response)

    def get_streamable_track(
        self, release_uuid: str, track_uuid: str
    ) -> models.StreamableTrackResponse:
        """Get a streamable MP3 file for the track.

        Args:
            release_uuid (str): The unique ID of the release. (models.Release.uuid)
            track_uuid (str): The unique ID of the track. (models.Track.uuid)

        Returns:
            models.StreamableTrackResponse: if successful, raise error with HTTP status_code and reason otherwise.
        """
        url = self.STREAM_TRACK.format(
            release_uuid=release_uuid,
            track_uuid=track_uuid,
        )
        params = {
            "noRedirect": True,
        }
        response = self.session.get(
            url=url,
            params=params,
        )
        if response.status_code == 200:
            return models.StreamableTrackResponse.from_json(json.dumps(response.json()))
        else:
            self.bad_response(response=response)

    def get_downloadable_track(
        self, release_uuid: str, track_uuid: str, format: str
    ) -> models.DownloadableTrackResponse:
        """Download the audio file of a track. Requires a Gold membership.

        Args:
            release_uuid (str): The unique ID of the release. (models.Release.uuid)
            track_uuid (str): The unique ID of the track. (models.Track.uuid)
            format (str): The preferred audio file format (supported: mp3_320, flac, wav)

        Returns:
            models.DownloadableTrackResponse: if successful, raise error with HTTP status_code and reason otherwise.
        """
        url = self.DOWNLOAD_TRACK.format(
            release_uuid=release_uuid,
            track_uuid=track_uuid,
        )
        params = {
            "noRedirect": True,
            "format": format,
        }
        response = self.session.get(
            url=url,
            params=params,
        )
        if response.status_code == 200:
            return models.DownloadableTrackResponse.from_json(
                json.dumps(response.json())
            )
        else:
            self.bad_response(response=response)

    def get_downloadable_extended(self, file_uuid: str) -> str:
        url = self.EXTENDED_TRACK.format(
            file_uuid=file_uuid,
        )
        params = {
            "download": True,
        }
        response = self.session.get(
            url=url,
            params=params,
        )
        if response.status_code == 200:
            print(response.json())
        else:
            self.bad_response(response=response)

    def get_related_releases(self, release_uuid: str) -> models.RelatedReleasesResponse:
        url = self.RELATED_RELEASES.format(
            release_uuid=release_uuid,
        )
        response = self.session.get(
            url=url,
        )
        if response.status_code == 200:
            return models.RelatedReleasesResponse.from_json(json.dumps(response.json()))
        else:
            self.bad_response(response=response)


# mkek = MonstercatAPI(email="", password="")
# s = mkek.sign_in()
# print(s)

# s = mkek.sign_in(email_uuid=s.auth_data.email.uuid)
# print(s)

# s = mkek.session.post("https://player.monstercat.app/api/me/two-factor/resend-sms")
# print(s)

mkek = MonstercatAPI()
mkek.sign_in_email(email=EMAIL, password=PASSWORD)

release = mkek.browse_releases(search="..Ready For It?", limit=1)
release = mkek.get_release(catalog_id=release.data[0].catalog_id)
# stream_track = mkek.get_streamable_track(
#     release_uuid=release.release.uuid, track_uuid=release.tracks[0].uuid
# )
# download_track = mkek.get_downloadable_track(
#     release_uuid=release.release.uuid,
#     track_uuid=release.tracks[0].uuid,
#     format="flac",
# )
related = mkek.get_related_releases(release.release.uuid)
print(related)
# print(release.tracks[0])
# print(stream_track)
# print(download_track)
